/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;

namespace DaveSexton.DocProject
{
	public static class PathFormat
	{
		public static readonly string DirectorySeparator = System.IO.Path.DirectorySeparatorChar.ToString();
		public static readonly string AltDirectorySeparator = System.IO.Path.AltDirectorySeparatorChar.ToString();
		public static readonly string FileNameReplacementCharacter = "_";
		public static readonly string UncPrefix = @"\\";
		public static readonly string WildcardSingleCharacter = "?";
		public static readonly string WildcardZeroOrMoreCharacters = "*";
		public static readonly string WildcardRecursiveDirectory = "**";
		public static readonly string WildcardRecursiveDirectorySection = WildcardRecursiveDirectory + @"\";

		public static bool IsDirectory(string path)
		{
			return path != null && (path = path.TrimEnd()).Length > 0 &&
				(path.EndsWith(DirectorySeparator, StringComparison.Ordinal) || path.EndsWith(AltDirectorySeparator, StringComparison.Ordinal));
		}

		public static bool IsValidDirectoryName(string name)
		{
			return name != null && (name = name.Trim()).Length > 0 && name.IndexOfAny(System.IO.Path.GetInvalidPathChars()) == -1
				&& !name.Contains(WildcardSingleCharacter) && !name.Contains(WildcardZeroOrMoreCharacters) && !name.Contains(WildcardRecursiveDirectory);
		}

		[System.Diagnostics.DebuggerHidden]
		public static bool IsValid(string path, bool mustBeRooted)
		{
			if (!string.IsNullOrEmpty(path))
			{
				try
				{
					// check IsPathRooted first so that it will verify whether the path contains invalid characters
					return System.IO.Path.IsPathRooted(path) || !mustBeRooted;
				}
				catch (ArgumentException) { }
			}

			return false;
		}

		public static string EnsureDirectory(string path)
		{
			if (string.IsNullOrEmpty(path))
				return DirectorySeparator;
			else if ((path = path.Trim()).Length > 0
				&& !path.EndsWith(DirectorySeparator, StringComparison.Ordinal) && !path.EndsWith(AltDirectorySeparator, StringComparison.Ordinal))
				return path + DirectorySeparator;
			else
				return path;
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")]
		public static string RemoveLeadingPathSeparator(string path)
		{
			if (path != null && (path = path.TrimStart()).Length > 0)
			{
				if (path.StartsWith(DirectorySeparator, StringComparison.Ordinal) || path.StartsWith(AltDirectorySeparator, StringComparison.Ordinal))
					path = path.Substring(1);
			}

			return path;
		}

		public static string ToPath(string path, bool ensureDirectory)
		{
			if (string.IsNullOrEmpty(path))
				return null;
			else
			{
				Uri uri;
				if (!Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out uri))
					return null;
				else
				{
					path = 
						(uri.IsAbsoluteUri)
						? (uri.IsFile || uri.IsUnc)
							? uri.LocalPath.Replace(System.IO.Path.AltDirectorySeparatorChar, System.IO.Path.DirectorySeparatorChar)
							: uri.AbsolutePath.Replace(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar)
						: System.Web.HttpUtility.UrlDecode(
								uri.ToString()).Replace(System.IO.Path.AltDirectorySeparatorChar, System.IO.Path.DirectorySeparatorChar);

					return (ensureDirectory)
						? EnsureDirectory(path)
						: path;
				}
			}
		}

		public static string Clean(string path)
		{
			if (path == null)
				return null;

			path = path.Trim();

			if (System.IO.Path.IsPathRooted(path))
				return path.Replace(System.IO.Path.AltDirectorySeparatorChar, System.IO.Path.DirectorySeparatorChar);
			else
				return path.Replace(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar);
		}

		public static string EnsureFileName(string name)
		{
			if (name == null)
				return null;

			name = name.Trim();

			if (name.Length == 0)
				return null;

			string invalid = new string(System.IO.Path.GetInvalidFileNameChars());

			StringBuilder builder = new StringBuilder(name.Length);

			foreach (char c in name)
			{
				if (char.IsPunctuation(c) || char.IsSymbol(c) || invalid.IndexOf(c) > -1)
					builder.Append(FileNameReplacementCharacter);
				else
					builder.Append(c);
			}

			return builder.ToString();
		}

		public static bool HasWildcard(string path)
		{
			return !string.IsNullOrEmpty(path) &&
				(path.Contains(WildcardSingleCharacter) || path.Contains(WildcardZeroOrMoreCharacters) || path.Contains(WildcardRecursiveDirectory));
		}

		public static string RemoveWildcards(string path)
		{
			if (string.IsNullOrEmpty(path))
				return null;
			else
				return path.Replace(WildcardSingleCharacter, "").Replace(WildcardZeroOrMoreCharacters, "");
		}

		/// <remarks>
		/// <para>
		/// <see cref="System.IO.Path.GetDirectoryName"/> does not return the computer name if a UNC path is specified without 
		/// a directory, but <see cref="SafeGetDirectoryName"/> will.
		/// </para>
		/// <para>
		/// For example, SafeGetDirectoryName(@"\\computer\file.txt") will return "\\computer".
		/// </para>
		/// </remarks>
		public static string SafeGetDirectoryName(string path)
		{
			if (string.IsNullOrEmpty(path))
				return null;

			string directory = System.IO.Path.GetDirectoryName(path);

			if (directory == null)
			{
				Uri uri = new Uri(path, UriKind.RelativeOrAbsolute);

				if (uri.IsAbsoluteUri && uri.IsUnc)
					directory = PathFormat.UncPrefix + uri.GetComponents(UriComponents.Host, UriFormat.Unescaped);
			}

			return directory;
		}

		public static string MakeLocal(string path, out bool wildcard, out bool recursiveDirectory)
		{
			if (string.IsNullOrEmpty(path))
			{
				wildcard = false;
				recursiveDirectory = false;
				return null;
			}

			string formattedPath = path.Trim();

			wildcard = formattedPath.Contains(WildcardSingleCharacter) || formattedPath.Contains(WildcardZeroOrMoreCharacters) || formattedPath.Contains(WildcardRecursiveDirectory);

			if (wildcard)
			{
				string recursiveRoot;
				bool isDirectory;

				if (!string.IsNullOrEmpty(System.IO.Path.GetExtension(path)))
				{
					recursiveRoot = System.IO.Path.GetDirectoryName(path) ?? "";		// null is returned when there is no directory information; e.g., when path consists of only a root UNC path
					isDirectory = false;
				}
				else if (path.EndsWith(DirectorySeparator, StringComparison.Ordinal) || path.EndsWith(AltDirectorySeparator, StringComparison.Ordinal))
				{
					recursiveRoot = System.IO.Path.GetDirectoryName(path) ?? "";		// null is returned when there is no directory information; e.g., when path consists of only a root UNC path
					isDirectory = true;
				}
				else
				{
					string name = System.IO.Path.GetFileName(formattedPath);

					if (name == WildcardZeroOrMoreCharacters)
					{
						formattedPath = formattedPath.Substring(0, formattedPath.Length - WildcardZeroOrMoreCharacters.Length);
						recursiveDirectory = false;
					}
					else if (name == WildcardRecursiveDirectory)
					{
						formattedPath = formattedPath.Substring(0, formattedPath.Length - WildcardRecursiveDirectory.Length);
						recursiveDirectory = true;
					}
					else
						recursiveDirectory = false;

					goto Return;
				}

				recursiveDirectory = System.IO.Path.GetFileName(recursiveRoot) == WildcardRecursiveDirectory;

				if (recursiveDirectory)
				{
					// The recursiveRoot variable will be an invalid UNC path when something like "\\computer\**\file.txt" is specified for the path argument since "**\" is removed.
					// SafeGetDirectoryName will return the UNC root even if there's no valid directory.
					string rootDirectory = SafeGetDirectoryName(recursiveRoot);

					formattedPath = (isDirectory)
						? EnsureDirectory(rootDirectory)
						: System.IO.Path.Combine(rootDirectory, System.IO.Path.GetFileName(path));
				}
			}
			else
				recursiveDirectory = false;

		Return:
			return MakeLocal(formattedPath);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1307:SpecifyStringComparison", MessageId = "System.String.StartsWith(System.String)")]
		public static string MakeLocal(string path)
		{
			if (path == null)
				return null;

			path = path.Trim();

			if (path.Length == 0)
				return string.Empty;

			if (!System.IO.Path.IsPathRooted(path))
				return path.Replace(System.IO.Path.AltDirectorySeparatorChar, System.IO.Path.DirectorySeparatorChar);

			bool unc = path.StartsWith("//") || path.StartsWith(@"\\");

			if (unc)
				path = path.Remove(0, 2);

			Stack<string> parts = new Stack<string>(path.Length / 2);
			StringBuilder part = new StringBuilder(128);
			bool movingUp = false, isFile = false;

			for (int i = 0; i < path.Length; i++)
			{
				char c = path[i];

				if (c == System.IO.Path.AltDirectorySeparatorChar || c == System.IO.Path.DirectorySeparatorChar)
				{
					isFile = false;

					if (part.Length > 0)
					{
						parts.Push(part.ToString());
						part.Remove(0, part.Length);
					}
					else
						movingUp = false;
				}
				else if (c == '.')
				{
					char previous = path[i - 1];

					// NOTE: When the first period of two or more running periods is parsed, variables are updated and the loop is advanced; therefore, checking for 
					// a period one character back will always indicate whether there are at least three periods in a row.
					if (movingUp && previous == '.')										// ... = up another directory
					{
						if (parts.Count > 0)
							parts.Pop();
					}
					else if ((previous == System.IO.Path.AltDirectorySeparatorChar || previous == System.IO.Path.DirectorySeparatorChar)
						&& i + 1 < path.Length && path[i + 1] == '.')			// .. = up 1 directory
					{
						movingUp = true;
						i += 1;		// skip next period

						if (parts.Count > 0)
							parts.Pop();
					}
					else if (!movingUp)
					{
						isFile = true;

						part.Append(c);
					}
				}
				else if (!movingUp)
					part.Append(c);
			}

			if (part.Length > 0)
				parts.Push(part.ToString());

			StringBuilder newPath = new StringBuilder(path.Length);

			if (unc)
				newPath.Append(UncPrefix);

			string[] array = parts.ToArray();
			Array.Reverse(array);

			foreach (string s in array)
			{
				newPath.Append(s);
				newPath.Append(DirectorySeparator);
			}

			if (isFile)
				newPath.Remove(newPath.Length - 1, 1);

			return newPath.ToString();
		}

		public static string MakeRelativeLocal(string rootPath, string targetPath)
		{
			if (rootPath == null)
				throw new ArgumentNullException("rootPath");

			if (targetPath == null)
				throw new ArgumentNullException("targetPath");

			Uri root = new Uri(rootPath, UriKind.Absolute);
			Uri target = new Uri(targetPath, UriKind.RelativeOrAbsolute);

			if (target.IsAbsoluteUri && (target.IsFile || target.IsUnc))
				target = root.MakeRelativeUri(target);

			// NOTE: Relative uri must be decoded since spaces are escaped.  Also, if the uri returned
			// is absolute the IsAbsoluteUri property will still be false and the ToString() method will
			// behave as a relative uri instead of an absolute uri (so the scheme is not returned).
			return PathFormat.MakeLocal(System.Web.HttpUtility.UrlDecode(target.ToString()));
		}

		[System.Diagnostics.DebuggerHidden]
		public static bool TryMakeRelativeLocal(string rootPath, string targetPath, out string relativePath)
		{
			if (rootPath == null)
				throw new ArgumentNullException("rootPath");

			if (targetPath == null)
				throw new ArgumentNullException("targetPath");

			Uri root;
			if (Uri.TryCreate(rootPath, UriKind.Absolute, out root))
			{
				Uri target;
				if (Uri.TryCreate(targetPath, UriKind.RelativeOrAbsolute, out target))
				{
					if (target.IsAbsoluteUri && (target.IsFile || target.IsUnc))
					{
						target = root.MakeRelativeUri(target);

						// NOTE: Relative uri must be decoded since spaces are escaped.  Also, if the uri returned
						// is absolute the IsAbsoluteUri property will still be false and the ToString() method will
						// behave as a relative uri instead of an absolute uri (so the scheme is not returned).
						relativePath = PathFormat.MakeLocal(System.Web.HttpUtility.UrlDecode(target.ToString()));
						return true;
					}
				}
			}
			
			relativePath = null;
			return false;
		}

		public static string MakeVirtual(string path)
		{
			if (path == null)
				return null;

			path = path.Trim().Replace(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar);

			if (!System.IO.Path.IsPathRooted(path) && !path.StartsWith(AltDirectorySeparator, StringComparison.Ordinal))
				path = AltDirectorySeparator + path;

			return path;
		}

		public static string InsertAllTypesDialogFilter(string allTypesFilterTextPrefix, params string[] filters)
		{
			if (string.IsNullOrEmpty(allTypesFilterTextPrefix))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "allTypesFilterTextPrefix");

			if (filters == null)
				throw new ArgumentNullException("filters");

			string filter = string.Join("|", filters);

			List<string> allFilters = new List<string>(GetUniqueDialogFilters(StringComparison.OrdinalIgnoreCase, filter));

			if (allFilters.Count > 0)
			{
				string allTypesFilter = string.Join(";", allFilters.ToArray());

				filter = string.Format(System.Globalization.CultureInfo.InvariantCulture,
					"{0} ({1})|{1}|{2}", allTypesFilterTextPrefix, allTypesFilter, filter);
			}

			return filter;
		}

		public static IEnumerable<string> GetUniqueDialogFilters(StringComparison comparisonType, string filter)
		{
			return GetUniqueDialogFilters(comparisonType, filter, false);
		}

		public static IEnumerable<string> GetUniqueDialogFilters(StringComparison comparisonType, string filter, bool additionalFiltersFirst, params string[] additionalFilters)
		{
			List<string> uniqueFilters = new List<string>(8);

			Predicate<string> predicate = delegate(string uniqueFilter)
			{
				return !uniqueFilters.Exists(delegate(string f) { return string.Equals(f, uniqueFilter, comparisonType); });
			};

			if (!additionalFiltersFirst && additionalFilters != null)
			{
				foreach (string uniqueFilter in GetUniqueDialogFiltersInternal(additionalFilters, predicate))
				{
					uniqueFilters.Add(uniqueFilter);
					yield return uniqueFilter;
				}
			}

			if (!string.IsNullOrEmpty(filter))
			{
				string[] filters = filter.Split('|');

				for (int i = 1; i < filters.Length; i += 2)
				{
					foreach (string uniqueFilter in GetUniqueDialogFiltersInternal(filters[i].Split(';'), predicate))
					{
						uniqueFilters.Add(uniqueFilter);
						yield return uniqueFilter;
					}
				}
			}

			if (!additionalFiltersFirst && additionalFilters != null)
			{
				foreach (string uniqueFilter in GetUniqueDialogFiltersInternal(additionalFilters, predicate))
				{
					uniqueFilters.Add(uniqueFilter);
					yield return uniqueFilter;
				}
			}
		}

		private static IEnumerable<string> GetUniqueDialogFiltersInternal(string[] filters, Predicate<string> predicate)
		{
			foreach (string unformattedFilter in filters)
			{
				if (!string.IsNullOrEmpty(unformattedFilter))
				{
					string filter = unformattedFilter.Trim();

					if (filter.Length > 0 && predicate(filter))
						yield return filter;
				}
			}
		}

		public static IEnumerable<string> GetUniqueDialogFilterExtensions(StringComparison comparisonType, string filter)
		{
			return GetUniqueDialogFilterExtensions(comparisonType, filter, false);
		}

		public static IEnumerable<string> GetUniqueDialogFilterExtensions(StringComparison comparisonType, string filter, bool additionalExtensionsFirst, params string[] additionalExtensions)
		{
			List<string> extensions = new List<string>(8);

			Predicate<string> predicate = delegate(string extension)
			{
				return !extensions.Exists(delegate(string e) { return string.Equals(e, extension, comparisonType); });
			};

			if (additionalExtensionsFirst && additionalExtensions != null)
			{
				foreach (string extension in GetUniqueDialogFilterExtensionsInternal(additionalExtensions, predicate))
				{
					extensions.Add(extension);
					yield return extension;
				}
			}

			if (!string.IsNullOrEmpty(filter))
			{
				string[] filters = filter.Split('|');

				for (int i = 1; i < filters.Length; i += 2)
				{
					foreach (string extension in GetUniqueDialogFilterExtensionsInternal(filters[i].Split(';'), predicate))
					{
						extensions.Add(extension);
						yield return extension;
					}
				}
			}

			if (!additionalExtensionsFirst && additionalExtensions != null)
			{
				foreach (string extension in GetUniqueDialogFilterExtensionsInternal(additionalExtensions, predicate))
				{
					extensions.Add(extension);
					yield return extension;
				}
			}
		}

		private static IEnumerable<string> GetUniqueDialogFilterExtensionsInternal(string[] filters, Predicate<string> predicate)
		{
			foreach (string filter in filters)
			{
				if (!string.IsNullOrEmpty(filter))
				{
					string extension = filter.Trim();

					if (extension.Length > 0)
					{
						int startExtensionIndex = extension.LastIndexOf('.');

						if (startExtensionIndex > -1)
						{
							extension = extension.Substring(startExtensionIndex).Trim();

							if (extension.Length > 0 && !extension.Contains(WildcardSingleCharacter) && !extension.Contains(WildcardZeroOrMoreCharacters) && predicate(extension))
								yield return extension;
						}
					}
				}
			}
		}
	}
}
